package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.config.BloomFilterConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.bouncycastle.pqc.crypto.gmss.GMSSLeaf;
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import springfox.documentation.spring.web.json.Json;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 切面类 内部包含所有的切面方法
 *
 * @author Jia Fa
 * @create 2021-12-13 17:51
 */
@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter rBloomFilter;

    /**
     * joinPoint.getArgs(); 获取方法参数
     * joinPoint.getTarget().getClass(); 获取目标类
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.atguigu.gmall.index.aspect.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 通过方法签名获取连接点方法对象
        Method method = signature.getMethod();
        // 获取方法上的注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        // 获取注解上的参数属性
        String prefix = gmallCache.prefix();

        //1 从缓存中查询是否存在,命中则直接返回
        // 获取目标方法的参数列表
        Object[] objects = joinPoint.getArgs();
        List<Object> args = Arrays.asList(objects);
        String key = prefix + args; //index:cates:[pid]

        // 布隆过滤器  比对查询id是否存在 不存在直接返回null
        boolean flag = this.rBloomFilter.contains(key);
        if (!flag){
            return null;
        }

        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            // 命中直接返回  method.getReturnType() 是返回值类型
            return JSON.parseObject(json, method.getReturnType());
        }

        //2 为防止缓存穿透添加分布式锁
        String lock = gmallCache.lock();
        RLock rLock = this.redissonClient.getLock(lock + args);//分布式锁 名字 lock:[pid]
        rLock.lock();
        try {
            //3 再从缓存中查询是否存在,命中则直接返回
            json = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json)) {
                // 命中直接返回  method.getReturnType() 是返回值类型
                return JSON.parseObject(json, method.getReturnType());
            }
            //4 执行目标方法
            Object result = joinPoint.proceed(joinPoint.getArgs());

            //5 将数据设置到缓存中 忽略值为null的情况 交给布隆过滤器
            if (result != null) {
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                this.redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeout, TimeUnit.MINUTES);
            }

            return result;
        } finally {
            // 解除分布式锁
            rLock.unlock();
        }
    }
}
